package cn.shanke.torrentmanagerserver.controller;

import cn.shanke.torrentmanagerserver.btclient.BtClient;
import cn.shanke.torrentmanagerserver.btclient.BtClients;
import cn.shanke.torrentmanagerserver.btclient.model.TmSessionInfo;
import cn.shanke.torrentmanagerserver.btclient.model.TmTorrentInfo;
import cn.shanke.torrentmanagerserver.btclient.model.TmTorrentInfoSimple;
import cn.shanke.torrentmanagerserver.config.AppConfig;
import cn.shanke.torrentmanagerserver.controller.common.ResponseModel;
import cn.shanke.torrentmanagerserver.model.*;
import cn.shanke.torrentmanagerserver.service.*;
import cn.shanke.torrentmanagerserver.util.CacheUtil;
import cn.shanke.torrentmanagerserver.util.FileUtil;
import cn.shanke.torrentmanagerserver.util.StringUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import nl.stil4m.transmission.api.domain.File;
import nl.stil4m.transmission.rpc.RpcException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping("/torrent")
public class TorrentController {
    private final Logger logger = LoggerFactory.getLogger(TorrentController.class);

    @Resource
    private TorrentService modelService;
    @Resource
    private TorrentInClientService torrentInClientService;
    @Resource
    private BTSiteService btSiteService;
    @Resource
    private CategoryService categoryService;
    @Resource
    private ClientService clientService;
    @Resource
    private ConfigService configService;
    @Resource
    private CacheUtil cacheUtil;
    @Resource
    private AppConfig appConfig;

    public void wrapPageQuery(Page page, QueryWrapper query, HttpServletRequest request) {
        String current = request.getParameter("current");
        String pageSize = request.getParameter("pageSize");
        String sortField = request.getParameter("sortField");
        String sortOrder = request.getParameter("sortOrder");

        page.setCurrent(StringUtils.hasText(current)?1:Long.parseLong(current));
        page.setSize(StringUtils.hasText(pageSize)?20:Long.parseLong(pageSize));

        if (!StringUtils.hasText(sortField)) { // "ascend" "descend"
            if ("descend".equals(sortOrder)) {
                query.orderByDesc(sortField);
            } else {
                query.orderByAsc(sortField);
            }
        }
    }

    @GetMapping("/list")
    public ResponseModel listModels(HttpServletRequest request,
        @RequestParam(value = "keyword",required = false) String keyword,
        @RequestParam(value = "category[]",required = false) String[] category,
        @RequestParam(value = "site[]",required = false) String[] site,
        @RequestParam(value = "client[]",required = false) String[] client,
        @RequestParam(value = "status[]",required = false) String[] status,
        @RequestParam(value = "linkDir[]",required = false) String[] linkDir
    ) {
        Page<Torrent> page = new Page<>();
        QueryWrapper<Torrent> query = new QueryWrapper<>();
        wrapPageQuery(page, query, request);

        if (!StringUtils.hasText(keyword)) {
            query.lambda().and(queryWrapper ->
                    queryWrapper.like(Torrent::getName, keyword).or().like(Torrent::getBtname, keyword)
            );
        }
        List<Long> categotyList  = StringUtil.arrayToLongList(category);
        if (!categotyList.isEmpty()) {
            query.lambda().in(Torrent::getCategoryid, categotyList);
        }
        List<Long> siteList  = StringUtil.arrayToLongList(site);
        if (!siteList.isEmpty()) {
            query.lambda().in(Torrent::getSiteid, siteList);
        }
        List<Long> clientList  = StringUtil.arrayToLongList(client);
        if (!clientList.isEmpty()) {
            query.lambda().in(Torrent::getClientid, clientList);
        }
        if (status != null && status.length > 0) {
            query.lambda().in(Torrent::getStatus, status);
        }
        if (linkDir != null && linkDir.length == 1) {
            if ("yes".equals(linkDir[0]))
                query.lambda().isNotNull(Torrent::getLinkDir);
            else
                query.lambda().isNull(Torrent::getLinkDir);
        }
        modelService.page(page, query);
        JSONObject data = new JSONObject();
        data.put("rows", page.getRecords());
        data.put("current", page.getCurrent());
        data.put("pageSize", page.getSize());
        data.put("total", page.getTotal());
        return ResponseModel.success().data(data);
    }

    @GetMapping(value = "/find/{id}")
    public ResponseModel getModelById(@PathVariable("id")Long id) {
        return ResponseModel.success().data(modelService.getById(id));
    }

    @PostMapping("/save")
    public ResponseModel saveModel(@RequestBody Torrent model) {
//        System.out.println(model);

        if (StringUtils.hasText(model.getTorrentFile()) && StringUtils.hasText(model.getAddress())) {
            return ResponseModel.failure("参数错误");
        }
        if (model.getId() == null) {
            // A.新增
            model.setAddtime(System.currentTimeMillis());
            model.setStatus(Torrent.STATUS_UNLINK);
        } else {
            // B.更新
            // 下列属性使用注解：updateStrategy = FieldStrategy.IGNORED
            // 且前台传参不包含下列属性，更新时会更新为空值，故从数据库读取并赋值
            Torrent oldModel = modelService.getById(model.getId());
            if (oldModel != null) {
                model.setBtid(oldModel.getBtid());
                model.setHashString(oldModel.getHashString());
                model.setBtname(oldModel.getBtname());
                model.setLinkDir(oldModel.getLinkDir());
            }
        }
        // 推送下载
        if (model.isStartOnAdd() && model.getClientid() != null) {
            model.setStatus(Torrent.STATUS_LINKING);
        }
        modelService.saveOrUpdate(model);

        if (model.getStatus() != null && model.getStatus().equals(Torrent.STATUS_LINKING)) {
            // 异步推送
            this.pushTorrentAsync(model);
        }
        return ResponseModel.success().data(model.getId());
    }

    /**
     * 推送种子
     * @param id
     * @param clientid
     * @param startNow
     * @return
     */
    @GetMapping("/pushToClient")
    public ResponseModel pushToClient(@RequestParam Long id
            , @RequestParam(required = false) Long clientid
            , @RequestParam(required = false, defaultValue = "false") boolean startNow
            , @RequestParam(required = false, defaultValue = "false") boolean enableSiteFolder
            , @RequestParam(required = false, defaultValue = "false") boolean enableCategoryFolder
    ) {
        Torrent model = modelService.getById(id);
        if (model != null && Torrent.STATUS_UNLINK.equals(model.getStatus())) {
            // 推送
            model.setStartNow(startNow);
            model.setClientid(clientid);
            model.setEnableSiteFolder(enableSiteFolder);
            model.setEnableCategoryFolder(enableCategoryFolder);
            try {
                TmTorrentInfo info = this.pushTorrent(model);
                return ResponseModel.success().data(info);
            } catch (RpcException | IOException e) {
//                    e.printStackTrace();
                return ResponseModel.failure("推送下载失败：" + e.getMessage());
            }
        } else {
            return ResponseModel.failure("当前状态无法推送");
        }
    }

    /**
     * 推送下载
     * @param model
     */
    private TmTorrentInfo pushTorrent(Torrent model) throws RpcException, IOException {
        logger.info(("开始推送下载"));
        TmTorrentInfo info = null;
        BtClient btClient = BtClients.get(model.getClientid() + "");
        if (btClient != null) {
            try {
                TmSessionInfo tsi = btClient.getSessionInfo();
                String downloadDir = tsi.getDownloadDir();
                if (!StringUtils.hasText(downloadDir)) {

//                    Path downloadPath = Paths.get(baseDownloadDir);
//                    if (!StringUtils.hasText(cacheUtil.getPathOfBTSite(model.getSiteid()))) {
//                        downloadPath = downloadPath.resolve(cacheUtil.getPathOfBTSite(model.getSiteid()));
//                    }
//                    if (!StringUtils.hasText(cacheUtil.getPathOfCategory(model.getCategoryid()))) {
//                        downloadPath = downloadPath.resolve(cacheUtil.getPathOfCategory(model.getCategoryid()));
//                    }
//                    System.out.println("下载地址：" + downloadPath.toFile().getAbsolutePath());

                    downloadDir = downloadDir
                            + (model.isEnableSiteFolder() ? cacheUtil.getPathOfBTSite(model.getSiteid()) : "")
                            + (model.isEnableCategoryFolder() ? cacheUtil.getPathOfCategory(model.getCategoryid()) : "");

                    if (!StringUtils.hasText(model.getTorrentFile())) {
                        // A.推送种子文件
                        Path filePath = Paths.get(appConfig.getLocation()).resolve(ResourceController.TORRENT_FILE_FOLDER).resolve(model.getTorrentFile());
                        if (Files.exists(filePath)) {
                            info = btClient.addTorrent(
                                    filePath,
                                    downloadDir, //downloadPath.toFile().getAbsolutePath(),
                                    model.isStartNow()
                            );
                        }
                    } else {
                        // B.推送下载地址
                        info = btClient.addTorrent(
                                model.getAddress(),
                                downloadDir, //downloadPath.toFile().getAbsolutePath(),
                                model.isStartNow()
                        );
                    }

                    if (info != null && !StringUtils.hasText(info.getId())) {
                        // 关联数据
                        if (StringUtils.hasText(model.getName())) {
                            model.setName(!StringUtils.hasText(info.getName())?info.getName():info.getHashString());
                        }
                        model.setBtid(info.getId());
                        model.setHashString(info.getHashString());
                        model.setBtname(info.getName());
                        model.setStatus(Torrent.STATUS_LINKED);
                        modelService.updateById(model);
                    }
                }
            } catch (RpcException | IOException e) {
//                e.printStackTrace();
                logger.error(("推送下载失败：" + e.getMessage()));
                model.setStatus(Torrent.STATUS_UNLINK);
                modelService.updateById(model);
                throw e;
            }
        }
        return info;
    }
    private final Executor pushPool = Executors.newFixedThreadPool(10);
    private void pushTorrentAsync(Torrent model) {
        pushPool.execute(()->{
            try {
                this.pushTorrent(model);
            } catch (RpcException | IOException e) {
//                e.printStackTrace();
            }
        });
    }

    @GetMapping("/delete/{id}")
    @Transactional
    public ResponseModel deleteModel(@PathVariable("id")Long id) {
        modelService.removeById(id);
        torrentInClientService.remove(new QueryWrapper<TorrentInClient>().lambda().eq(TorrentInClient::getTorrentid, id));
        return ResponseModel.success();
    }

    @PostMapping("/update")
    public ResponseModel updateModel(@RequestBody JSONObject requestJo) {
        Integer id = requestJo.getInteger("id");
        Integer siteid = requestJo.getInteger("siteid");
        Integer categoryid = requestJo.getInteger("categoryid");
        Integer clientid = requestJo.getInteger("clientid");
        UpdateWrapper<Torrent> wrapper = new UpdateWrapper<>();
        wrapper.eq("id", id);
        if (siteid != null) {
            wrapper.set("siteid", siteid);
        }
        if (categoryid != null) {
            wrapper.set("categoryid", categoryid);
        }
        if (clientid != null) {
            wrapper.set("clientid", clientid);
        }
        modelService.update(wrapper);
        return ResponseModel.success();
    }

    /**
     * 从下载服务器获取种子详情
     * @param id
     * @return
     */
    @GetMapping("/infoInClient")
    public ResponseModel infoInClient(
            String id
    ) {
        Torrent model = modelService.getById(id);
        BtClient btClient = BtClients.get(model.getClientid() + "");
        TmTorrentInfo info;
        JSONObject jo = null;
        try {
            info = btClient.getTorrentInfo(model.getHashString());
            if (info != null && !StringUtils.hasText(info.getId())){
                TmSessionInfo tsi = btClient.getSessionInfo();
                String baseDownlaodPath = tsi.getDownloadDir();
                jo = JSON.parseObject(JSON.toJSONString(info));
                jo.put("client", btClient.getConfig());
                jo.put("baseDownlaodPath", baseDownlaodPath);
            }
        } catch (RpcException e) {
            e.printStackTrace();
            return ResponseModel.failure(e.getMessage());
        }
        return ResponseModel.success().data(jo);
    }

    /**
     * 从下载服务器获取种子列表
     * @param clientid
     * @return
     */
    @GetMapping("/listInClient")
    public ResponseModel listInClient(
            String clientid
    ) {
        BtClient btClient = BtClients.get(clientid);
        List<TmTorrentInfoSimple> list = null;
        try {
            list = btClient.getAllTorrentInfo();
        } catch (RpcException e) {
            e.printStackTrace();
            return ResponseModel.failure(e.getMessage());
        }
        return ResponseModel.success().data(list);
    }

    /**
     * 从下载服务器获取信息并保存
     * @param clientid
     * @return
     */
    @GetMapping("/downloadFromClient")
    public ResponseModel downloadFromClient(
            String clientid
    ) {
        BtClient btClient = BtClients.get(clientid);
        List<TmTorrentInfoSimple> list = null;
        try {
            list = btClient.getAllTorrentInfo();
            List<BTSite> sites = btSiteService.list();
            List<Category> categories = categoryService.list();
            if (!list.isEmpty()) {
                for (TmTorrentInfoSimple ttis : list) {
                    modelService.syncOne(ttis, Long.valueOf(clientid), sites, categories);
                }
            }
        } catch (RpcException e) {
            e.printStackTrace();
            return ResponseModel.failure(e.getMessage());
        }
        return ResponseModel.success().data(list);
    }



    /**
     * 将种子文件硬链接到媒体库
     * @param id
     * @return
     */
    @GetMapping("/linkToLibrary")
    public ResponseModel linkToLibrary(
            String id
    ) {
        if (!appConfig.isDocker() && StringUtils.hasText(configService.getCurrent().getManagePathHost())) {
            return ResponseModel.failure("请先完成[基础配置-目录映射]");
        }

        Torrent model = modelService.getById(id);
        JSONObject data = new JSONObject();
        if (model != null) {
            Client client = clientService.getById(model.getClientid());
            String clientVolumeHost =  client.getVolumeHost();
            String clientVolumeContainer = client.getVolumeContainer();

            if (StringUtils.hasText(clientVolumeHost) || StringUtils.hasText(clientVolumeContainer)) {
                return ResponseModel.failure("请先完成所在下载器的[下载目录映射]");
            }

            BtClient btClient = BtClients.get(model.getClientid() + "");
            try {
                TmTorrentInfo tmti = btClient.getTorrentInfo(model.getHashString());
                String downloadDir = tmti.getDownloadDir();
                data.put("sourceDir", downloadDir.replaceFirst(clientVolumeHost, clientVolumeContainer));
                if (tmti != null) {
                   List<File> files = tmti.getFiles();
                   data.put("files", files);
                }
            } catch (RpcException e ) {
//                e.printStackTrace();
                return ResponseModel.failure(e.getMessage());
            }

//            System.out.println("clientVolumeHost: " + clientVolumeHost);
//            System.out.println("clientVolumeContainer: " + clientVolumeContainer);
//            System.out.println("downloadDir: " + downloadDir);
//            files.stream().forEach(file->{
//                System.out.println(JSON.toJSONString(file));
//            });

        }
        return ResponseModel.success().data(data);
    }

    @PostMapping("/doLinkToLibrary")
    public ResponseModel doLinkToLibrary(@RequestBody JSONObject req) {

        Long torrentId = req.getLong("torrentId");
        String sourceDir = req.getString("sourceDir");
        String linkDir = req.getString("linkDir");
        JSONArray linkRows = req.getJSONArray("linkRows");
        boolean move = req.getBooleanValue("move");

//        System.out.println("sourceDir:" + sourceDir);
//        System.out.println("linkDir:" + linkDir);
//        System.out.println(linkRows);

        Torrent model = modelService.getById(torrentId);
        if (move) {
            // 从下载服务器停止
            if (model != null && !StringUtils.hasText(model.getHashString())) {
                BtClient btClient = BtClients.get(model.getClientid() + "");
                try {
                    List<String> ids = new ArrayList<String>(){{add(model.getHashString());}};
                    btClient.stop(ids);
                } catch (RpcException e) {
                    e.printStackTrace();
                    return ResponseModel.failure(e.getMessage());
                }
            }
        }

        String managePath = configService.getCurrent().getManagePath();
        String managePathHost = configService.getCurrent().getManagePathHost();

        for (int i = 0; i < linkRows.size(); i++) {
            JSONObject linkRow = linkRows.getJSONObject(i);

            if (!linkRow.getBooleanValue("selected")) continue;

            String sourceFile = Paths.get(appConfig.isDocker() ? sourceDir : sourceDir.replaceFirst(managePath, managePathHost))
                    .resolve(linkRow.getString("sourceFile")).toFile().getAbsolutePath();
            String linkFile = Paths.get(appConfig.isDocker() ? linkDir : linkDir.replaceFirst(managePath, managePathHost))
                    .resolve(linkRow.getString("linkFile")).toFile().getAbsolutePath();
            boolean replace = linkRow.getBooleanValue("replace");

            logger.info("to link >>>>>>>>>>>>>");
            logger.info(sourceFile);
            logger.info("↓↓↓↓↓↓↓↓");
            logger.info(linkFile);

            if (Files.notExists(Paths.get(sourceFile))) {
                linkRow.put("status", 201); // "原文件不存在"
            } else {
                // 覆盖的话先删除原有目标
                if (replace) {
                    try {
                        Files.deleteIfExists(Paths.get(linkFile));
                    } catch (IOException e) {
                    }
                }

                if (Files.exists(Paths.get(linkFile))) {
                    linkRow.put("status", 202); // 目标文件已经存在
                } else {
                    try {
                        if (move) {
                            // 移动
                            FileUtil.moveTo(sourceFile, linkFile);
                        } else {
                            // 硬链接
                            FileUtil.createLink(sourceFile, linkFile);
                        }
                        linkRow.put("status", 1);
                    } catch (IOException e) {
                        logger.error("do link error:" + e.getMessage());
                        linkRow.put("status", e.getMessage());
                    }
                }
            }
        }

        modelService.update(new UpdateWrapper<Torrent>().lambda().set(Torrent::getLinkDir, linkDir).eq(Torrent::getId, torrentId));
        return ResponseModel.success().data(linkRows);
    }

    @PostMapping("/setLocation")
    public ResponseModel setLocation(@RequestBody JSONObject req) {
        Long    id       = req.getLong("id");
        String  location = req.getString("location");
        boolean move     = req.getBooleanValue("move");
        boolean verify   = req.getBooleanValue("verify");

        Torrent model = modelService.getById(id);
        if (model != null && !StringUtils.hasText(model.getHashString())) {
            BtClient btClient = BtClients.get(model.getClientid() + "");
            try {
                List<String> ids = new ArrayList<String>(){{add(model.getHashString());}};
                btClient.setTorrentLocation(ids, location, move);
                if (verify) {
                    TimeUnit.SECONDS.sleep(2);
                    btClient.verify(ids);
                }
            } catch (RpcException e) {
                logger.error(e.getMessage());
                return ResponseModel.failure(e.getMessage());
            } catch (InterruptedException e) {
                logger.error(e.getMessage());
            }
        }
        return ResponseModel.success();
    }

    /**
     * 可远程从下载服务器删除
     * @param req
     * @return
     */
    @PostMapping("/remove")
    @Transactional
    public ResponseModel remove(@RequestBody JSONObject req) {
        Long    id      = req.getLong("id");
        boolean remove  = req.getBooleanValue("remove");  // 从本系统删除
        boolean del     = req.getBooleanValue("del");     // 从下载服务器删除
        boolean delFile = req.getBooleanValue("delFile"); // 同时删除文件

        Torrent model = modelService.getById(id);

        // 1.从下载服务器删除
        if (del) {
            if (model != null && !StringUtils.hasText(model.getHashString())) {
                BtClient btClient = BtClients.get(model.getClientid() + "");
                try {
//                    TmTorrentInfo info = btClient.getTorrentInfo(model.getHashString());
//                    System.out.println(info.getDownloadDir());
                    List<String> ids = new ArrayList<String>(){{add(model.getHashString());}};
                    btClient.removeTorrent(ids, delFile);
                } catch (RpcException e) {
                    e.printStackTrace();
                    return ResponseModel.failure(e.getMessage());
                }
                model.setBtid(null);
                model.setHashString(null);
                model.setBtname(null);
                model.setStatus(Torrent.STATUS_UNLINK);
            }
        }

        // 2.从本系统删除
        if (remove) {
            modelService.removeById(id);
            torrentInClientService.remove(new QueryWrapper<TorrentInClient>().lambda().eq(TorrentInClient::getTorrentid, id));
        } else {
            if (model != null)
                modelService.updateById(model);
        }
        return ResponseModel.success();
    }

    @PostMapping("/doAction")
    public ResponseModel doAction(@RequestBody JSONObject req) {
        Long    id       = req.getLong("id");
        String  action   = req.getString("action");

        Torrent model = modelService.getById(id);
        if (model != null && !StringUtils.hasText(model.getHashString())) {
            BtClient btClient = BtClients.get(model.getClientid() + "");
            try {
                List<String> ids = new ArrayList<String>(){{add(model.getHashString());}};
                if (BtClient.ACTION_START.equals(action)) {
                    btClient.start(ids);
                } else if (BtClient.ACTION_STOP.equals(action)) {
                    btClient.stop(ids);
                } else if (BtClient.ACTION_VERIFY.equals(action)) {
                    btClient.verify(ids);
                } else {
                    logger.warn("无可用操作");
                }
            } catch (RpcException e) {
                logger.error(e.getMessage());
                return ResponseModel.failure(e.getMessage());
            }
        }
        return ResponseModel.success();
    }
}
